package org.codehaus.activemq.message;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import javax.jms.JMSException;
import javax.jms.MessageEOFException;
import javax.jms.MessageFormatException;
import javax.jms.MessageNotReadableException;
import javax.jms.MessageNotWriteableException;
import javax.jms.StreamMessage;

public class ActiveMQStreamMessage extends ActiveMQMessage
  implements StreamMessage
{
  private DataOutputStream dataOut;
  private ByteArrayOutputStream bytesOut;
  private DataInputStream dataIn;
  private int bytesToRead = -1;

  public int getPacketType()
  {
    return 10;
  }

  public ActiveMQMessage shallowCopy()
    throws JMSException
  {
    ActiveMQStreamMessage other = new ActiveMQStreamMessage();
    initializeOther(other);
    try {
      other.setBodyAsBytes(getBodyAsBytes());
    }
    catch (IOException e) {
      JMSException jmsEx = new JMSException("setBodyAsBytes() failed");
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    return other;
  }

  public ActiveMQMessage deepCopy()
    throws JMSException
  {
    ActiveMQStreamMessage other = new ActiveMQStreamMessage();
    initializeOther(other);
    try {
      if (getBodyAsBytes() != null) {
        byte[] data = new byte[getBodyAsBytes().length];
        System.arraycopy(getBodyAsBytes(), 0, data, 0, data.length);
        other.setBodyAsBytes(data);
      }
    }
    catch (IOException e) {
      JMSException jmsEx = new JMSException("setBodyAsBytes() failed");
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    return other;
  }

  public void clearBody()
    throws JMSException
  {
    super.clearBody();
    this.dataOut = null;
    this.dataIn = null;
    this.bytesOut = null;
  }
  public boolean readBoolean() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 2) {
        throw new MessageFormatException("Not enough data left to read value");
      }

      this.dataIn.mark(10);
      int type = this.dataIn.read();
      if (type == 5) {
        return this.dataIn.readBoolean();
      }
      if (type == 4) {
        return Boolean.valueOf(this.dataIn.readUTF()).booleanValue();
      }

      this.dataIn.reset();
      throw new MessageFormatException(" not a boolean type");
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public byte readByte() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 2) {
        throw new MessageFormatException("Not enough data left to read value");
      }

      this.dataIn.mark(10);
      int type = this.dataIn.read();
      if (type == 7) {
        return this.dataIn.readByte();
      }
      if (type == 4) {
        return Byte.valueOf(this.dataIn.readUTF()).byteValue();
      }

      this.dataIn.reset();
      throw new MessageFormatException(" not a byte type");
    }
    catch (NumberFormatException mfe)
    {
      try {
        this.dataIn.reset();
      }
      catch (IOException ioe) {
        jmsEx = new JMSException("reset failed");
        jmsEx.setLinkedException(ioe);
      }
      throw mfe;
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public short readShort() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 2) {
        throw new MessageFormatException("Not enough data left to read value");
      }

      this.dataIn.mark(17);
      int type = this.dataIn.read();
      if (type == 8) {
        return this.dataIn.readShort();
      }
      if (type == 7) {
        return (short)this.dataIn.readByte();
      }
      if (type == 4) {
        return Short.valueOf(this.dataIn.readUTF()).shortValue();
      }

      this.dataIn.reset();
      throw new MessageFormatException(" not a short type");
    }
    catch (NumberFormatException mfe)
    {
      try {
        this.dataIn.reset();
      }
      catch (IOException ioe) {
        jmsEx = new JMSException("reset failed");
        jmsEx.setLinkedException(ioe);
      }
      throw mfe;
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public char readChar() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 2) {
        throw new MessageFormatException("Not enough data left to read value");
      }

      this.dataIn.mark(17);
      int type = this.dataIn.read();
      if (type == 6) {
        return this.dataIn.readChar();
      }

      this.dataIn.reset();
      throw new MessageFormatException(" not a char type");
    }
    catch (NumberFormatException mfe)
    {
      try {
        this.dataIn.reset();
      }
      catch (IOException ioe) {
        jmsEx = new JMSException("reset failed");
        jmsEx.setLinkedException(ioe);
      }
      throw mfe;
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public int readInt() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 2) {
        throw new MessageFormatException("Not enough data left to read value");
      }

      this.dataIn.mark(33);
      int type = this.dataIn.read();
      if (type == 9) {
        return this.dataIn.readInt();
      }
      if (type == 8) {
        return this.dataIn.readShort();
      }
      if (type == 7) {
        return this.dataIn.readByte();
      }
      if (type == 4) {
        return Integer.valueOf(this.dataIn.readUTF()).intValue();
      }

      this.dataIn.reset();
      throw new MessageFormatException(" not an int type");
    }
    catch (NumberFormatException mfe)
    {
      try {
        this.dataIn.reset();
      }
      catch (IOException ioe) {
        jmsEx = new JMSException("reset failed");
        jmsEx.setLinkedException(ioe);
      }
      throw mfe;
    }
    catch (EOFException e)
    {
       jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public long readLong() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 2) {
        throw new MessageFormatException("Not enough data left to read value");
      }

      this.dataIn.mark(65);
      int type = this.dataIn.read();
      if (type == 10) {
        return this.dataIn.readLong();
      }
      if (type == 9) {
        return this.dataIn.readInt();
      }
      if (type == 8) {
        return this.dataIn.readShort();
      }
      if (type == 7) {
        return this.dataIn.readByte();
      }
      if (type == 4) {
        return Long.valueOf(this.dataIn.readUTF()).longValue();
      }

      this.dataIn.reset();
      throw new MessageFormatException(" not a long type");
    }
    catch (NumberFormatException mfe)
    {
      try {
        this.dataIn.reset();
      }
      catch (IOException ioe) {
        jmsEx = new JMSException("reset failed");
        jmsEx.setLinkedException(ioe);
      }
      throw mfe;
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public float readFloat() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 2) {
        throw new MessageFormatException("Not enough data left to read value");
      }

      this.dataIn.mark(33);
      int type = this.dataIn.read();
      if (type == 11) {
        return this.dataIn.readFloat();
      }
      if (type == 4) {
        return Float.valueOf(this.dataIn.readUTF()).floatValue();
      }

      this.dataIn.reset();
      throw new MessageFormatException(" not a float type");
    }
    catch (NumberFormatException mfe)
    {
      try {
        this.dataIn.reset();
      }
      catch (IOException ioe) {
        jmsEx = new JMSException("reset failed");
        jmsEx.setLinkedException(ioe);
      }
      throw mfe;
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public double readDouble() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 2) {
        throw new MessageFormatException("Not enough data left to read value");
      }

      this.dataIn.mark(65);
      int type = this.dataIn.read();
      if (type == 12) {
        return this.dataIn.readDouble();
      }
      if (type == 11) {
        return this.dataIn.readFloat();
      }
      if (type == 4) {
        return Double.valueOf(this.dataIn.readUTF()).doubleValue();
      }

      this.dataIn.reset();
      throw new MessageFormatException(" not a double type");
    }
    catch (NumberFormatException mfe)
    {
      try {
        this.dataIn.reset();
      }
      catch (IOException ioe) {
        jmsEx = new JMSException("reset failed");
        jmsEx.setLinkedException(ioe);
      }
      throw mfe;
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public String readString() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 2) {
        throw new MessageFormatException("Not enough data left to read value");
      }

      this.dataIn.mark(65);
      int type = this.dataIn.read();
      if (type == 13) {
        return null;
      }
      if (type == 4) {
        return this.dataIn.readUTF();
      }
      if (type == 10) {
        return new Long(this.dataIn.readLong()).toString();
      }
      if (type == 9) {
        return new Integer(this.dataIn.readInt()).toString();
      }
      if (type == 8) {
        return new Short(this.dataIn.readShort()).toString();
      }
      if (type == 7) {
        return new Byte(this.dataIn.readByte()).toString();
      }
      if (type == 11) {
        return new Float(this.dataIn.readFloat()).toString();
      }
      if (type == 12) {
        return new Double(this.dataIn.readDouble()).toString();
      }
      if (type == 5) {
        return (this.dataIn.readBoolean() ? Boolean.TRUE : Boolean.FALSE).toString();
      }
      if (type == 6) {
        return new Character(this.dataIn.readChar()).toString();
      }

      this.dataIn.reset();
      throw new MessageFormatException(" not a String type");
    }
    catch (NumberFormatException mfe)
    {
      try {
        this.dataIn.reset();
      }
      catch (IOException ioe) {
        jmsEx = new JMSException("reset failed");
        jmsEx.setLinkedException(ioe);
      }
      throw mfe;
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public int readBytes(byte[] value) throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (value == null) {
        throw new NullPointerException();
      }
      if (this.bytesToRead == 0) {
        this.bytesToRead = -1;
        return -1;
      }
      if (this.bytesToRead > 0) {
        if (value.length >= this.bytesToRead) {
          this.bytesToRead = 0;
          return this.dataIn.read(value, 0, this.bytesToRead);
        }

        this.bytesToRead -= value.length;
        return this.dataIn.read(value);
      }

      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 1) {
        throw new MessageFormatException("Not enough data left to read value");
      }
      this.dataIn.mark(value.length + 1);
      int type = this.dataIn.read();
      if (this.dataIn.available() < 1) {
        return -1;
      }
      if (type != 3) {
        throw new MessageFormatException("Not a byte array");
      }
      int len = this.dataIn.readInt();

      if (len >= value.length) {
        this.bytesToRead = (len - value.length);
        return this.dataIn.read(value);
      }

      this.bytesToRead = 0;
      return this.dataIn.read(value, 0, len);
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }
  public Object readObject() throws JMSException {
    initializeReading();
    JMSException jmsEx;
    try {
      if (this.dataIn.available() == 0) {
        throw new MessageEOFException("reached end of data");
      }
      if (this.dataIn.available() < 1) {
        throw new MessageFormatException("Not enough data left to read value - avaialable = " + this.dataIn.available());
      }

      this.dataIn.mark(65);
      int type = this.dataIn.read();
      if (type == 13) {
        return null;
      }
      if (type == 4) {
        return this.dataIn.readUTF();
      }
      if (type == 10) {
        return new Long(this.dataIn.readLong());
      }
      if (type == 9) {
        return new Integer(this.dataIn.readInt());
      }
      if (type == 8) {
        return new Short(this.dataIn.readShort());
      }
      if (type == 7) {
        return new Byte(this.dataIn.readByte());
      }
      if (type == 11) {
        return new Float(this.dataIn.readFloat());
      }
      if (type == 12) {
        return new Double(this.dataIn.readDouble());
      }
      if (type == 5) {
        return this.dataIn.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
      }
      if (type == 6) {
        return new Character(this.dataIn.readChar());
      }
      if (type == 3) {
        int len = this.dataIn.readInt();
        byte[] value = new byte[len];
        this.dataIn.read(value);
        return value;
      }

      this.dataIn.reset();
      throw new MessageFormatException("unknown type");
    }
    catch (NumberFormatException mfe)
    {
      try {
        this.dataIn.reset();
      }
      catch (IOException ioe) {
        jmsEx = new JMSException("reset failed");
        jmsEx.setLinkedException(ioe);
      }
      throw mfe;
    }
    catch (EOFException e)
    {
      jmsEx = new MessageEOFException(e.getMessage());
      jmsEx.setLinkedException(e);
      throw jmsEx;
    }
    catch (IOException e) {
      jmsEx = new MessageFormatException(e.getMessage());
      jmsEx.setLinkedException(e);
    }throw jmsEx;
  }

  public void writeBoolean(boolean value)
    throws JMSException
  {
    initializeWriting();
    try {
      this.dataOut.write(5);
      this.dataOut.writeBoolean(value);
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeByte(byte value)
    throws JMSException
  {
    initializeWriting();
    try {
      this.dataOut.write(7);
      this.dataOut.writeByte(value);
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeShort(short value)
    throws JMSException
  {
    initializeWriting();
    try {
      this.dataOut.write(8);
      this.dataOut.writeShort(value);
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeChar(char value)
    throws JMSException
  {
    initializeWriting();
    try {
      this.dataOut.write(6);
      this.dataOut.writeChar(value);
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeInt(int value)
    throws JMSException
  {
    initializeWriting();
    try {
      this.dataOut.write(9);
      this.dataOut.writeInt(value);
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeLong(long value)
    throws JMSException
  {
    initializeWriting();
    try {
      this.dataOut.write(10);
      this.dataOut.writeLong(value);
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeFloat(float value)
    throws JMSException
  {
    initializeWriting();
    try {
      this.dataOut.write(11);
      this.dataOut.writeFloat(value);
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeDouble(double value)
    throws JMSException
  {
    initializeWriting();
    try {
      this.dataOut.write(12);
      this.dataOut.writeDouble(value);
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeString(String value)
    throws JMSException
  {
    initializeWriting();
    try {
      if (value == null) {
        this.dataOut.write(13);
      }
      else {
        this.dataOut.write(4);
        this.dataOut.writeUTF(value);
      }
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeBytes(byte[] value)
    throws JMSException
  {
    writeBytes(value, 0, value.length);
  }

  public void writeBytes(byte[] value, int offset, int length)
    throws JMSException
  {
    initializeWriting();
    try {
      this.dataOut.write(3);
      this.dataOut.writeInt(length);
      this.dataOut.write(value, offset, length);
    }
    catch (IOException ioe) {
      JMSException jmsEx = new JMSException(ioe.getMessage());
      jmsEx.setLinkedException(ioe);
      throw jmsEx;
    }
  }

  public void writeObject(Object value)
    throws JMSException
  {
    initializeWriting();
    if (value == null) {
      try {
        this.dataOut.write(13);
      }
      catch (IOException ioe) {
        JMSException jmsEx = new JMSException(ioe.getMessage());
        jmsEx.setLinkedException(ioe);
        throw jmsEx;
      }
    }
    else if ((value instanceof String)) {
      writeString(value.toString());
    }
    else if ((value instanceof Character)) {
      writeChar(((Character)value).charValue());
    }
    else if ((value instanceof Boolean)) {
      writeBoolean(((Boolean)value).booleanValue());
    }
    else if ((value instanceof Byte)) {
      writeByte(((Byte)value).byteValue());
    }
    else if ((value instanceof Short)) {
      writeShort(((Short)value).shortValue());
    }
    else if ((value instanceof Integer)) {
      writeInt(((Integer)value).intValue());
    }
    else if ((value instanceof Float)) {
      writeFloat(((Float)value).floatValue());
    }
    else if ((value instanceof Double)) {
      writeDouble(((Double)value).doubleValue());
    }
    else if ((value instanceof byte[]))
      writeBytes((byte[])value);
  }

  public void reset()
    throws JMSException
  {
    this.readOnlyMessage = true;
    if (this.dataOut != null) {
      try {
        this.dataOut.flush();
        super.setBodyAsBytes(this.bytesOut.toByteArray());
        this.dataOut.close();
      }
      catch (IOException ioe) {
        JMSException jmsEx = new JMSException("reset failed: " + ioe.getMessage());
        jmsEx.setLinkedException(ioe);
        throw jmsEx;
      }
    }
    this.bytesOut = null;
    this.dataIn = null;
    this.dataOut = null;
  }

  public void setBodyAsBytes(byte[] bodyAsBytes)
  {
    super.setBodyAsBytes(bodyAsBytes);
    this.dataOut = null;
    this.dataIn = null;
  }

  public byte[] getBodyAsBytes()
    throws IOException
  {
    if (this.dataOut != null) {
      this.dataOut.flush();
      super.setBodyAsBytes(this.bytesOut.toByteArray());
      this.dataOut.close();
      this.dataOut = null;
    }
    return super.getBodyAsBytes();
  }

  private void initializeWriting() throws MessageNotWriteableException
  {
    if (this.readOnlyMessage) {
      throw new MessageNotWriteableException("This message is in read-only mode");
    }
    if (this.dataOut == null) {
      this.bytesOut = new ByteArrayOutputStream();
      this.dataOut = new DataOutputStream(this.bytesOut);
    }
  }

  private void initializeReading() throws MessageNotReadableException
  {
    if (!this.readOnlyMessage)
      throw new MessageNotReadableException("This message is in write-only mode");
    try
    {
      byte[] data = super.getBodyAsBytes();
      if ((this.dataIn == null) && (data != null)) {
        ByteArrayInputStream bytesIn = new ByteArrayInputStream(data);
        this.dataIn = new DataInputStream(bytesIn);
      }
    }
    catch (IOException e) {
      MessageNotReadableException mnr = new MessageNotReadableException("getBodyAsBytes failed");
      mnr.setLinkedException(e);
      throw mnr;
    }
  }
}